/*
 * console.c
 *
 *  Created on: 2016-10-28
 *      Author: m
 */

#include "console.h"
#include "keyboard.h"
#include "global.h"
#include "sub.h"
#include "string.h"
#include "fake_shell.h"

/*

     orig_addr ----> +-------------------+ -----------------------
                     |xxxxxxxxxxxxxxxxxxx|  /|                  |
                     |xxxxxxxxxxxxxxxxxxx| / |                  |
                     |xxxxxxxxxxxxxxxxxxx|/  |                  |
     cur_start_addr->|xxxxxxxxxxxxxxxxxxx|   |              v_mem_limit
                     |xxxxxxxxxxxxxxxxxxx|   |                  |
                     |xxxxxxxxxxxxxxxxxxx|   |-display          |
                     |xxxx(cursor)       |   |                  |
                     |                   |\  |                  |
                     |                   | \ |                  |
                     |-------------------|  \|                  |
                     |                   |                      |
                     |                   |                      |
                     |___________________| _____________________|

 */

struct special_key_state {
    char is_alt_down:1;
    char is_shift_down:1;
    char is_ctrl_down:1;
    char is_cap_lock_down:1;
    char is_num_lock_down:1;
    char is_scroll_lock_down:1;
    char dummy:2;
} special_key_info;
typedef struct {
    char c;
    char c_shift;
} VKEY2CHAR;
VKEY2CHAR svkey2char[128] = {
    {0, 0}, // 0
    {'a', 'A'}, //KEY_A(1)
    {'b', 'B'}, //KEY_B(2)
    {'c', 'C'}, //KEY_C(3)
    {'d', 'D'}, //KEY_D(4)
    {'e', 'E'}, //KEY_E(5)
    {'f', 'F'}, //KEY_F(6)
    {'g', 'G'}, //KEY_G(7)
    {'h', 'H'}, //KEY_H(8)
    {'i', 'I'}, //KEY_I(9)
    {'j', 'J'}, //KEY_J(10)
    {'k', 'K'}, //KEY_K(11)
    {'l', 'L'}, //KEY_L(12)
    {'m', 'M'}, //KEY_M(13)
    {'n', 'N'}, //KEY_N(14)
    {'o', 'O'}, //KEY_O(15)
    {'p', 'P'}, //KEY_P(16)
    {'q', 'Q'}, //KEY_Q(17)
    {'r', 'R'}, //KEY_R(18)
    {'s', 'S'}, //KEY_S(19)
    {'t', 'T'}, //KEY_T(20)
    {'u', 'U'}, //KEY_U(21)
    {'v', 'V'}, //KEY_V(22)
    {'w', 'W'}, //KEY_W(23)
    {'x', 'X'}, //KEY_X(24)
    {'y', 'Y'}, //KEY_Y(25)
    {'z', 'Z'}, //KEY_Z(26)
    {'0', ')'}, //KEY_0(27)
    {'1', '!'}, //KEY_1(28)
    {'2', '@'}, //KEY_2(29)
    {'3', '#'}, //KEY_3(30)
    {'4', '$'}, //KEY_4(31)
    {'5', '%'}, //KEY_5(32)
    {'6', '^'}, //KEY_6(33)
    {'7', '&'}, //KEY_7(34)
    {'8', '*'}, //KEY_8(35)
    {'9', '('}, //KEY_9(36)
    {'`', '~'}, //KEY_GRAVE(37)
    {'-', '_'}, //KEY_MINUS(38)
    {'=', '+'}, //KEY_EQUAL(39)
    {'\\', '|'}, //KEY_BACKSLASH(40)
    {0, 0}, //KEY_BACKSPACE(41)
    {0, 0}, //KEY_SPACE(42)
    {0, 0}, //KEY_TAB(43)
    {0, 0}, //KEY_CAPSLOCK(44)
    {0, 0}, //KEY_LEFTSHIFT(45)
    {0, 0}, //KEY_LEFTCTRL(46)
    {0, 0}, //KEY_LEFT_WIN(47)
    {0, 0}, //KEY_LEFTALT(48)
    {0, 0}, //KEY_RIGHTSHIFT(49)
    {0, 0}, //KEY_RIGHTCTRL(50)
    {0, 0}, //KEY_RIGHT_WIN(51)
    {0, 0}, //KEY_RIGHTALT(52)
    {0, 0}, //KEY_MENU(53)
    {0, 0}, //KEY_ENTER(54)
    {0, 0}, //KEY_ESC(55)
    {0, 0}, //KEY_F1(56)
    {0, 0}, //KEY_F2(57)
    {0, 0}, //KEY_F3(58)
    {0, 0}, //KEY_F4(59)
    {0, 0}, //KEY_F5(60)
    {0, 0}, //KEY_F6(61)
    {0, 0}, //KEY_F7(62)
    {0, 0}, //KEY_F8(63)
    {0, 0}, //KEY_F9(64)
    {0, 0}, //KEY_F10(65)
    {0, 0}, //KEY_F11(66)
    {0, 0}, //KEY_F12(67)
    {0, 0}, //KEY_PRINT_SCREEN(68)
    {0, 0}, //KEY_SCROLLLOCK(69)
    {0, 0}, //KEY_PAUSE(70)
    {'[', '{'}, //KEY_LEFTBRACE(71)
    {0, 0}, //KEY_INSERT(72)
    {0, 0}, //KEY_HOME(73)
    {0, 0}, //KEY_PAGEUP(74)
    {0, 0}, //KEY_DELETE(75)
    {0, 0}, //KEY_END(76)
    {0, 0}, //KEY_PAGEDOWN(77)
    {0, 0}, //KEY_UP(78)
    {0, 0}, //KEY_LEFT(79)
    {0, 0}, //KEY_DOWN(80)
    {0, 0}, //KEY_RIGHT(81)
    {0, 0}, //KEY_NUMLOCK(82)
    {'/', '/'}, //KEY_KPSLASH(83)
    {'*', '*'}, // KEY_KPASTERISK(84)
    {'-', '-'}, //KEY_KPMINUS(85)
    {'+', '+'}, //KEY_KPPLUS(86)
    {0, 0}, //KEY_KPENTER(87)
    {'.', '.'}, //KEY_KPDOT(88)
    {'0', '0'}, //KEY_KP0(89)
    {'1', '1'}, //KEY_KP1(90)
    {'2', '2'}, //KEY_KP2(91)
    {'3', '3'}, //KEY_KP3(92)
    {'4', '4'}, //KEY_KP4(93)
    {'5', '5'}, //KEY_KP5(94)
    {'6', '6'}, //KEY_KP6(95)
    {'7', '7'}, //KEY_KP7(96)
    {'8', '8'}, //KEY_KP8(97)
    {'9', '9'}, //KEY_KP9(98)
    {']', '}'}, //KEY_RIGHTBRACE(99)
    {';', ':'}, //KEY_SEMICOLON(100)
    {'\'', '"'}, //KEY_APOSTROPHE(101)
    {',', '<'}, //KEY_COMMA(102)
    {'.', '>'}, //KEY_DOT(103)
    {'/', '?'}, //KEY_SLASH(104)
    {0, 0}, // 105
    {0, 0}, // 106
    {0, 0}, // 107
    {0, 0}, // 108
    {0, 0}, // 109
    {0, 0}, // 110
    {0, 0}, // 111
    {0, 0}, // 112
    {0, 0}, // 113
    {0, 0}, // 114
    {0, 0}, // 115
    {0, 0}, // 116
    {0, 0}, // 117
    {0, 0}, // 118
    {0, 0}, // 119
    {0, 0}, // 120
    {0, 0}, // 121
    {0, 0}, // 122
    {0, 0}, // 123
    {0, 0}, // 124
    {0, 0}, // 125
    {0, 0}, // 126
    {0, 0}, // 127
};

void init_console(CONSOLE* con, u32 v_mem_base, u32 v_mem_limit) {
    con->cur_start_addr = v_mem_base;
    con->orig_addr = v_mem_base;
    con->cursor = v_mem_base;
    con->v_mem_limit = v_mem_limit;
}

int console_clean_screen(CONSOLE* con) {
    for(int i = 0; i < con->v_mem_limit; i+= 2) {
        char* write_pos = (char*)(i + con->orig_addr + V_MEM_BASE);
        *write_pos++ = ' ';
        *write_pos = g_text_attr; //color
    }
    con->cursor = con->orig_addr;
    console_set_cursor(con->cursor);
    return 0;
}

int console_set_cursor(u32 cursor) {
    cursor /= 2;
    di();
    out_byte(CRTC_ADDR_REG, CURSOR_H);
    out_byte(CRTC_DATA_REG, (cursor >> 8) & 0xFF);
    out_byte(CRTC_ADDR_REG, CURSOR_L);
    out_byte(CRTC_DATA_REG, cursor & 0xFF);
    si();
    return 0;
}

void console_set_start_addr(u16 start_addr) {
    start_addr /= 2;
    di();
    out_byte(CRTC_ADDR_REG, START_ADDR_H_REG);
    out_byte(CRTC_DATA_REG, (start_addr >> 8) & 0xFF);
    out_byte(CRTC_ADDR_REG, START_ADDR_L_REG);
    out_byte(CRTC_DATA_REG, start_addr & 0xFF);
    si();
}

int console_out_char(CONSOLE* con, char c) {
    char* write_pos = (char*)(con->cursor + V_MEM_BASE);

    if(con->cursor >= con->orig_addr + con->v_mem_limit) {
        return -1;
    }

    *write_pos++ = c;
    *write_pos = g_text_attr; //color
    con->cursor += 2;
    if(con == &g_console_tab[g_cur_tty_idx]) {
        console_set_cursor(con->cursor);
    }
    return 0;
}

static int update_special_key_state(u8 ev, u8 vkey) {
    if(EV_KEY == ev) {
        switch(vkey) {
        case KEY_LEFTSHIFT:
        case KEY_RIGHTSHIFT:
            special_key_info.is_shift_down = 1;
            return 1;
        case KEY_LEFTALT:
        case KEY_RIGHTALT:
            special_key_info.is_alt_down = 1;
            return 1;
        case KEY_LEFTCTRL:
        case KEY_RIGHTCTRL:
            special_key_info.is_ctrl_down = 1;
            return 1;
        case KEY_CAPSLOCK:
            special_key_info.is_cap_lock_down = 1;
            return 1;
        case KEY_NUMLOCK:
            special_key_info.is_num_lock_down = 1;
            return 1;
        case KEY_SCROLLLOCK:
            special_key_info.is_scroll_lock_down = 1;
            return 1;
        default:
            return 0;
        }
    } else if (EV_REL == ev) {
        switch(vkey) {
        case KEY_LEFTSHIFT:
        case KEY_RIGHTSHIFT:
            special_key_info.is_shift_down = 0;
            return 1;
        case KEY_LEFTALT:
        case KEY_RIGHTALT:
            special_key_info.is_alt_down = 0;
            return 1;
        case KEY_LEFTCTRL:
        case KEY_RIGHTCTRL:
            special_key_info.is_ctrl_down = 0;
            return 1;
        case KEY_CAPSLOCK:
            special_key_info.is_cap_lock_down = 0;
            return 1;
        case KEY_NUMLOCK:
            special_key_info.is_num_lock_down = 0;
            return 1;
        case KEY_SCROLLLOCK:
            special_key_info.is_scroll_lock_down = 0;
            return 1;
        default:
            return 0;
        }
    }
    return 0;
}

void console_scroll(CONSOLE* con, int direction, int row) { //row todo
    if(SCROLL_TEXT_UP == direction) {
        con->cur_start_addr += 160;
    } else if(SCROLL_TEXT_DOWN == direction){
        con->cur_start_addr -= 160;
    }

    if(con->cur_start_addr < con->orig_addr) {//显存越界
        con->cur_start_addr = con->orig_addr;
    } else if(con->cur_start_addr >= con->orig_addr + con->v_mem_limit) {
        con->cur_start_addr -= 160;
    }

    if(con->cur_start_addr > con->cursor) {//光标越界
        con->cur_start_addr -= 160;
    }

    console_set_start_addr(con->cur_start_addr);
}

int console_on_enter(CONSOLE* con) {
    int cursor = con->cursor;
    if(cursor >= con->orig_addr + con->v_mem_limit) {
        return -1;
    }
    con->cursor = ((con->cursor + 160) / 160) * 160;
    if(con->cursor >= con->cur_start_addr + 160 * 25) {
        console_scroll(con, SCROLL_TEXT_UP, 1);
    }
    console_set_cursor(con->cursor);
    return 0;
}
static int on_special_key_action(CONSOLE* con, u8 vkey) {
    if (special_key_info.is_shift_down) {
        if(KEY_UP == vkey) {
            console_scroll(con, SCROLL_TEXT_UP, 1);
            return 1;
        } else if(KEY_DOWN == vkey) {
            console_scroll(con, SCROLL_TEXT_DOWN, 1);
            return 1;
        }
    }

    if(special_key_info.is_shift_down) {
        switch(vkey) {
        case KEY_F1:
        case KEY_F2:
        case KEY_F3:
            g_cur_tty_idx = vkey - KEY_F1;
            con = &g_console_tab[g_cur_tty_idx];
            console_set_start_addr((u16)con->cur_start_addr);
            console_set_cursor(con->cursor);
            return 1;
        }
    }

    if(KEY_ENTER == vkey || KEY_KPENTER == vkey) {
        console_on_enter(con);
        return 1;
    } else if (KEY_BACKSPACE == vkey) {
        con->cursor -= 2;
        char* write_pos = (char*)(con->cursor + V_MEM_BASE);
        *write_pos++ = ' ';
        *write_pos = g_text_attr; //color
        console_set_cursor(con->cursor);
        return 1;
    }
    return 0;
}

static void on_key_down(CONSOLE* con, u8 vkey) {
    u8 c;
    if(update_special_key_state(EV_KEY, vkey)) {
        return;
    }

    if(on_special_key_action(con, vkey)) {
        on_usr_kbhit_specail(vkey);
    } else {
        if(special_key_info.is_shift_down) {
            c = svkey2char[vkey].c_shift;
        } else {
            c = svkey2char[vkey].c;
        }

        if(isprint(c)) {
            console_out_char(con, c);
        }
        on_usr_kbhit_c(c);
    }


}

static void on_key_up(CONSOLE* con, u8 vkey) {
    if(update_special_key_state(EV_REL, vkey)) {
        return;
    }
}

void console_on_key_evnet(CONSOLE* con, u8 xkey) {
    u8 ev = (xkey & 0x80) ? EV_REL : EV_KEY;
    u8 vkey = xkey & 0x7F;
    if (EV_KEY == ev) { //key down
        on_key_down(con, vkey);
    } else {
        on_key_up(con, vkey);
    }
}

int write_impl(int fd, void* buf, int count, PROCESS* requre_proc) {
    CONSOLE* con = &g_console_tab[requre_proc->tty_no];
    char* c = buf;
    for(int i = 0; i < count; ++i) {
        if('\n' == c[i]) {
            console_on_enter(con);
        } else {
            console_out_char(con, c[i]);
        }
    }
    return 0;
}
